package top.zhangjianyong.blog.service.impl;

import static top.zhangjianyong.blog.enums.RespCode.*;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import top.zhangjianyong.blog.ai.client.BlogVectorStoreClient;
import top.zhangjianyong.blog.ai.model.BlogData;
import top.zhangjianyong.blog.entity.dto.BlogCategoryDTO;
import top.zhangjianyong.blog.entity.dto.BlogDTO;
import top.zhangjianyong.blog.entity.dto.BlogTagDTO;
import top.zhangjianyong.blog.entity.po.*;
import top.zhangjianyong.blog.exception.ApiException;
import top.zhangjianyong.blog.mapper.BlogMapper;
import top.zhangjianyong.blog.params.blog.*;
import top.zhangjianyong.blog.response.ApiResponse;
import top.zhangjianyong.blog.service.*;

/**
 * 博客
 *
 * @author zhangjianyong
 * @since 2024/7/31 下午8:47
 */
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements BlogService {

  @Lazy @Resource private BlogDetailService blogDetailService;
  @Lazy @Resource private TagService tagService;
  @Lazy @Resource private BlogTagService blogTagService;
  @Lazy @Resource private CategoryService categoryService;
  @Lazy @Resource private BlogVectorStoreClient blogVectorStoreClient;

  @Override
  public ApiResponse page(BlogPageParam param) throws ExecutionException, InterruptedException {
    MPJLambdaWrapper<Blog> wrapper =
        createBlogWrapper()
            // .eq(isPortal, Blog::getStatus, "1")
            .and(
                StrUtil.isNotBlank(param.keyword()),
                (w) ->
                    w.apply(
                            "MATCH(b.title,b.summary) AGAINST({0} IN BOOLEAN MODE)",
                            param.keyword())
                        .or()
                        .apply(
                            "MATCH(bd.mk_content) AGAINST({0} IN BOOLEAN MODE)", param.keyword()))
            // 新增：根据status过滤
            .eq(param.status() != null, Blog::getStatus, param.status())
            .orderByDesc(
                StrUtil.isNotBlank(param.keyword()),
                "MATCH (b.title,b.summary) AGAINST ('" + param.keyword() + "' IN BOOLEAN MODE)",
                "MATCH (bd.mk_content) AGAINST ('" + param.keyword() + "' IN BOOLEAN MODE)")
            // .orderByDesc(isPortal && StrUtil.isBlank(param.keyword()), Blog::getTopFlag)
            .orderByDesc(Blog::getCreateTime);
    Page<BlogDTO> page =
        baseMapper.selectJoinPage(
            new Page<>(param.pageNum(), param.pageSize()), BlogDTO.class, wrapper);
    List<BlogDTO> records = page.getRecords();
    if (CollectionUtils.isEmpty(records)) {
      return ApiResponse.success(page);
    }
    assembleBlogDTO(records);
    return ApiResponse.success(page);
  }

  @Override
  public ApiResponse portalPage(BlogPageParam param)
      throws ExecutionException, InterruptedException {
    if (StrUtil.isNotBlank(param.keyword()) && SqlInjectionUtils.check(param.keyword())) {
      return ApiResponse.fail(ERROR_MYBATIS);
    }
    Page<BlogDTO> page =
        baseMapper.portalPage(new Page<>(param.pageNum(), param.pageSize()), param);
    List<BlogDTO> records = page.getRecords();
    if (CollectionUtils.isEmpty(records)) return ApiResponse.success(page);
    assembleBlogDTO(records);
    return ApiResponse.success(page);
  }

  @Override
  public ApiResponse topList(BlogTopListParam param)
      throws ExecutionException, InterruptedException {
    List<BlogDTO> list = baseMapper.topList(param);
    assembleBlogDTO(list);
    return ApiResponse.success(list);
  }

  private void assembleBlogDTO(List<BlogDTO> records)
      throws ExecutionException, InterruptedException {
    Map<Long, BlogDTO> blogMap =
        records.stream().collect(Collectors.toMap(BlogDTO::getId, b -> b, (a, b) -> a));
    // 组装标签数据
    List<CompletableFuture<BlogTagDTO>> tagFutures =
        records.stream().map(Blog::getId).map(tagService::listByBlogAsync).toList();
    // 组装分类路径
    List<CompletableFuture<BlogCategoryDTO>> categoryFutures =
        records.stream().map(BlogDTO::getCategoryId).map(categoryService::getPathAsync).toList();
    // 获取标签数据
    for (Future<BlogTagDTO> future : tagFutures) {
      BlogTagDTO tagDTO = future.get();
      Optional.ofNullable(blogMap.get(tagDTO.getBlogId()))
          .ifPresent(blog -> blog.setTags(tagDTO.getTags()));
    }
    // 获取分类数据
    Map<Long, BlogCategoryDTO> categoryDTOMap = new HashMap<>();
    for (Future<BlogCategoryDTO> future : categoryFutures) {
      BlogCategoryDTO categoryDTO = future.get();
      categoryDTOMap.put(categoryDTO.getCategoryId(), categoryDTO);
    }
    records.forEach(
        blogDTO -> {
          BlogCategoryDTO blogCategoryDTO = categoryDTOMap.get(blogDTO.getCategoryId());
          blogDTO.setCategories(blogCategoryDTO.getCategories());
        });
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public ApiResponse remove(BlogRemoveParam param) {
    Blog blog = getById(param.id());
    Long detailId = Optional.ofNullable(blog).map(Blog::getDetailId).orElse(null);

    boolean remove = removeById(param.id());
    if (!remove) return ApiResponse.fail(BLOG_REMOVE_ERROR);
    blogDetailService.removeById(detailId);
    blogTagService.removeByBlog(param.id());
    // 删除向量库
    blogVectorStoreClient.deleteBlog(Collections.singletonList(param.id()));
    return ApiResponse.success();
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public ApiResponse saveOrUpdate(BlogSaveOrUpdateParam param) {
    // 查询数据库中的博客信息及博客详情
    Blog one = null == param.id() ? null : getById(param.id());
    Long detailId = Optional.ofNullable(one).map(Blog::getDetailId).orElse(null);
    BlogDetail blogDetail = blogDetailService.saveOrUpdate(detailId, param.mkContent());

    Blog blog = new Blog();
    BeanUtils.copyProperties(param, blog);
    if (null == blog.getId()) {
      // 新增博客时初始化数据
      blog.setStatus(0);
      blog.setReadCount(0);
      blog.setLikes(0L);
      blog.setViews(0L);
      blog.setDetailId(blogDetail.getId());
    }
    blog.setStatus(0);
    // 保存博客
    boolean saveBlog = null == blog.getId() ? save(blog) : updateById(blog);
    if (!saveBlog) {
      throw new ApiException(BLOG_SAVE_OR_UPDATE_ERROR);
    }

    // 保存标签
    List<Tag> list = tagService.saveBatch(param.tags());
    List<Long> tagIds = list.stream().map(Tag::getId).toList();

    boolean saveTags = blogTagService.saveBatch(blog.getId(), tagIds);
    if (!saveTags) {
      throw new ApiException(BLOG_TAG_SAVE_OR_UPDATE_ERROR);
    }
    // 删除向量库
    blogVectorStoreClient.deleteBlog(Collections.singletonList(blog.getId()));
    return ApiResponse.success(blog);
  }

  @Override
  public void clearCategory(Long categoryId) {
    lambdaUpdate().eq(Blog::getCategoryId, categoryId).set(Blog::getCategoryId, null).update();
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public ApiResponse statusChange(BlogStateChangeParam param) {
    BlogDTO blog = getDTOById(param.id());
    if (null == blog) {
      return ApiResponse.fail(BLOG_NOT_FUND);
    }
    if (0 == blog.getStatus()) {
      return ApiResponse.fail(BLOG_NOT_PUSH_ERROR);
    }
    boolean update =
        lambdaUpdate()
            .eq(Blog::getId, param.id())
            .set(Blog::getStatus, param.status())
            .update(new Blog());
    if (!update) return ApiResponse.fail(BLOG_STATUS_CHANGE_ERROR);
    // 根据状态保存或者删除向量库
    switch (param.status()) {
      case 1:
        blogVectorStoreClient.saveBlogToVectorStore(BlogData.from(blog));
        break;
      case 2:
        blogVectorStoreClient.deleteBlog(Collections.singletonList(blog.getId()));
        break;
    }

    return ApiResponse.success();
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public ApiResponse push(BlogPushParam param) {
    BlogDTO blog = getDTOById(param.id());
    if (null == blog) return ApiResponse.fail(BLOG_NOT_FUND);

    boolean update =
        lambdaUpdate()
            .eq(Blog::getId, param.id())
            .eq(Blog::getStatus, 0) // 初始状态才能发布
            .set(Blog::getStatus, 1)
            .set(Blog::getPushTime, LocalDateTime.now())
            .update(new Blog());
    if (!update) return ApiResponse.fail(BLOG_PUSH_ERROR);

    // 保存博客到向量库
    blogVectorStoreClient.saveBlogToVectorStore(BlogData.from(blog));

    return ApiResponse.success();
  }

  private BlogDTO getDTOById(@NotNull(message = "博客id不能为空") Long id) {
    Blog blog = getById(id);
    if (blog == null) return null;
    BlogDTO blogDTO = new BlogDTO();
    BeanUtils.copyProperties(blog, blogDTO);
    BlogDetail blogDetail = blogDetailService.getById(blog.getDetailId());
    blogDTO.setMkContent(blogDetail.getMkContent());
    return blogDTO;
  }

  @Override
  public ApiResponse top(BlogTopParam param) {
    return lambdaUpdate()
            .eq(Blog::getId, param.id())
            .set(Blog::getTopFlag, param.topFlag())
            .update(new Blog())
        ? ApiResponse.success()
        : ApiResponse.fail(BLOG_TOP_ERROR);
  }

  @Override
  public ApiResponse get(BlogGetParam param) {
    MPJLambdaWrapper<Blog> wrapper = createBlogWrapper().eq(Blog::getId, param.id());
    BlogDTO blogDTO = baseMapper.selectJoinOne(BlogDTO.class, wrapper);
    if (null == blogDTO) return ApiResponse.fail(BLOG_NOT_FUND);

    BlogTagDTO blogTagDTO = tagService.listByBlog(blogDTO.getId());
    blogDTO.setTags(blogTagDTO.getTags());

    BlogCategoryDTO path = categoryService.getPath(blogDTO.getCategoryId());
    blogDTO.setCategories(path.getCategories());

    return ApiResponse.success(blogDTO);
  }

  @Override
  public List<Blog> listByTag(Long tagId) {
    MPJLambdaWrapper<Blog> wrapper =
        new MPJLambdaWrapper<>(Blog.class, "b")
            .leftJoin(BlogTag.class, BlogTag::getBlogId, Blog::getId)
            .eq(BlogTag::getTagId, tagId);
    return baseMapper.selectJoinList(Blog.class, wrapper);
  }

  @Override
  public Long countByTag(Long tagId) {
    MPJLambdaWrapper<Blog> wrapper =
        new MPJLambdaWrapper<>(Blog.class, "b")
            .leftJoin(BlogTag.class, BlogTag::getBlogId, Blog::getId)
            .eq(BlogTag::getTagId, tagId)
            .eq(Blog::getStatus, 1)
            .eq(Blog::getIsDel, 0);
    return baseMapper.selectJoinCount(wrapper);
  }

  private MPJLambdaWrapper<Blog> createBlogWrapper() {
    return new MPJLambdaWrapper<>(Blog.class, "b")
        .checkSqlInjection() // sql注入检查
        .selectAll(Blog.class)
        .selectAssociation(Category.class, BlogDTO::getCategory)
        .selectAssociation(
            BlogDetail.class, BlogDetail::getMkContent, map -> map.result(BlogDetail::getMkContent))
        .leftJoin(Category.class, Category::getId, Blog::getCategoryId)
        .leftJoin(BlogDetail.class, "bd", BlogDetail::getId, Blog::getDetailId);
  }

  @Override
  public long countByUser(Long userId) {
    Long count =
        lambdaQuery()
            .eq(Blog::getCreateUser, userId)
            .eq(Blog::getStatus, 1)
            .eq(Blog::getIsDel, 0)
            .count();
    return count == null ? 0 : count;
  }

  @Override
  public LocalDateTime getLastBlogPushTime(Long userId) {
    return baseMapper.getLastBlogPushTime(userId);
  }

  @Override
  public void addVisit(Long blogId, Integer type) {
    if (type == null) return;
    switch (type) {
      case 0:
        baseMapper.addViews(blogId);
        break;
      case 1:
        baseMapper.addLikes(blogId);
        break;
      default:
        break;
    }
  }

  @Override
  public long countByCategory(Long categoryId) {
    Long count =
        lambdaQuery()
            .eq(Blog::getCategoryId, categoryId)
            .eq(Blog::getStatus, 1) // 只统计已发布的博客
            .eq(Blog::getIsDel, 0) // 只统计未删除的博客
            .count();
    return count == null ? 0L : count;
  }

  @Override
  public List<BlogDTO> listPublished() {
    return baseMapper.listPublished();
  }
}
